這篇要介紹Kotlin的輕量級依賴注入框架Koin。
為了在單元測試解除外部相依,我們使用了依賴注入的方式,例如在MVP的架構下,Activity需要在初始化Repository時注入Presenter。這將造成Activity的程式變得複雜。一般情況,Activity應該不需要知道Repository的。
class MainActivity : AppCompatActivity() {
    private lateinit var presenter: Presenter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val repository = Repository()
        //注入repository
        presenter = Presenter(repository)
        val string = presenter.getString()
        println("log:$string")
    }
}
我們就來使用Koin依賴注入框架解決這個問題。
在build.gradle 加上
// Koin for Kotlin
implementation "org.koin:koin-core:$koin_version"
// Koin extended & experimental features
implementation "org.koin:koin-core-ext:$koin_version"
// Koin for Unit tests
testImplementation "org.koin:koin-test:$koin_version"
// Koin for Java developers
implementation "org.koin:koin-java:$koin_version"
// Koin for Android
implementation "org.koin:koin-android:$koin_version"
// Koin Android Scope features
implementation "org.koin:koin-android-scope:$koin_version"
// Koin Android ViewModel features
implementation "org.koin:koin-android-viewmodel:$koin_version"
// Koin Android Experimental features
implementation "org.koin:koin-android-ext:$koin_version"
在這個範例裡有著MainActivity、Presenter、Repository。

Module 是一個容器,儲存了需要注入的物件實例化的方式,
以這個例子,我們想在Activity注入Presenter,那麼就需要在Module定義如何建立Presenter的Instance。
koinModule.kt
val koinModule = module {
    factory {
        Presenter(Repository())
    }
}
接著需要初始化Koin,在Application.onCreate中使用startKoin。新增一個Application,在這裡startKoin裡放入剛剛新增的module
class KoinSampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin{
            androidLogger()
            androidContext(this@KoinSampleApplication)
            modules(koinModule)
        }
    }
}

新增完Application,AndroidManifest.xml 記得要加入。
<application
        android:name=".KoinSampleApplication"
/>
回到Activity,開始使用koin的方式注入。在 Presenter 後面加上 get。就會產生Presenter的Instance。這樣Activity的程式碼就乾淨許多了。
在要實現注入的地方用get取得實例
private val presenter: Presenter = get()
class MainActivity : AppCompatActivity() {
    //這裡不需要再注入Repository了
    private val presenter: Presenter = get()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val string = presenter.getString()
        println("log:$string")
    }
}
也可以用 by inject()做延遲注入。
private val presenter: Presenter by inject()
回到MVVM上一個範例。我們就來看要怎麼在ViewModel使用Koin。
原作法在建立ViewModel時,為了要達到依賴注入。在Activity會需要注入ProductAPI及ProductRepository
val productAPI = ProductAPI()
val productRepository = ProductRepository(productAPI)
productViewModel =
    ViewModelProviders.of(this, ProductViewModelFactory(productRepository)).get(ProductViewModel::class.java)
新增AppModule.kt,在這裡提供ProductRepository
val appModule = module {
    viewModel {
        val productAPI = ProductAPI()
        val productRepository = ProductRepository(productAPI)
        ProductViewModel(productRepository)
    }
}
新增一個Application,在onCreate裡,startKoin 裡放入剛剛新增的appModule。
class MVVMKoinApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin { modules(listOf(appModule)) }
    }
}
這邊要記得AndroidManifest要把新增的Application加入
<application
    android:name=".MVVMKoinApplication”
/>
最後回到Activity,Koin支援viewModel的注入方式。只要改加上 by viewModel() 就會注入了。
private val productViewModel: ProductViewModel by viewModel(),這樣Activity就不會需要在這裡注入ProductRepository了。
class ProductActivity : AppCompatActivity() {
    private val productId = "pixel3"
    //加上by viewModel
    private val productViewModel: ProductViewModel by viewModel()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_product)
        val dataBinding = DataBindingUtil.setContentView<ActivityProductBinding>(this, R.layout.activity_product)
        //改用Koin,註解以下
//        val productAPI = ProductAPI()
//        val productRepository = ProductRepository(productAPI)
//        productViewModel =
//            ViewModelProviders.of(this,     ProductViewModelFactory(productRepository)).get(ProductViewModel::class.java)
        dataBinding.productViewModel = productViewModel
        dataBinding.lifecycleOwner = this
}
這樣就完成使用koin注入。原本的這幾行,也可以刪掉了。
//        val productAPI = ProductAPI()
//        val productRepository = ProductRepository(productAPI)
//        productViewModel =
//            ViewModelProviders.of(this,     ProductViewModelFactory(productRepository)).get(ProductViewModel::class.java)
依賴注入框架,除了koin另一個比較有名的就是dagger了。但Koin在使用上比起dagger簡單。如果你正在使用dagger,也可以考慮移轉到koin。
範例下載:
https://github.com/evanchen76/KoinSample
https://github.com/evanchen76/mvvmkoinsample
出版書:
Android TDD 測試驅動開發:從 UnitTest、TDD 到 DevOps 實踐
線上課程:
Android 動畫入門到進階
Android UI 進階實戰(Material Design Component)